#include "loader.h"
#include "comm/cpu_instr.h"
#include "comm/elf.h"

// 参考资料：https://wiki.osdev.org/ATA_PIO_Mode
static void read_disk(uint32_t sector, uint32_t sector_count, uint8_t *buf) {
    outb(0x1F6, 0xE0);
    outb(0x1F2, (uint8_t)(sector_count >> 8));
    outb(0x1F3, (uint8_t)(sector >> 24));
    outb(0x1F4, 0);
    outb(0x1F5, 0);

    outb(0x1F2, (uint8_t)sector_count);
    outb(0x1F3, (uint8_t)(sector));
    outb(0x1F4, (uint8_t)(sector >> 8));
    outb(0x1F5, (uint8_t)(sector >> 16));

    outb(0x1F7, 0x24);

    uint16_t *data_buf = (uint16_t *)buf;
    while (sector_count--) {
        // 硬盘未就绪或数据未准备好
        while ((inb(0x1F7) & 0x88) != 0x8);

        for (int i = 0; i < SECTOR_SIZE / 2; i ++) {
            *data_buf++ = inw(0x1F0);
        }
    }
    
}

// 解析elf文件，提取内容到相应的内存中去
// 参考资料：https://wiki.osdev.org/ELF
static uint32_t reload_elf_file(uint8_t * file_buffer) {
    Elf32_Ehdr *elf_hdr = (Elf32_Ehdr *)file_buffer;
    if ((elf_hdr->e_ident[0] != ELF_MAGIC) || (elf_hdr->e_ident[1] != 'E') 
        || (elf_hdr->e_ident[2] != 'L') || (elf_hdr->e_ident[3] != 'F')) {
        return 0;
    }

    // 找到程序头表
    for (int i = 0; i < elf_hdr->e_phnum; i++) {
        Elf32_Phdr *phdr = (Elf32_Phdr *)(file_buffer + elf_hdr->e_phoff) + i;
        if (phdr->p_type != PT_LOAD) {
            continue;
        }
        // 找到程序头表指向的需要加载到内存中的段
        uint8_t *src = file_buffer + phdr->p_offset;
        uint8_t *dest = (uint8_t *)phdr->p_paddr;
        // 把这些段加载到内存中去
        for (int j = 0; j < phdr->p_filesz; j++) {
            *dest++ = *src++;
        }
        // 特殊考虑BSS段的加载情况
        dest = (uint8_t *)phdr->p_paddr + phdr->p_filesz;
        for (int j = 0; j < phdr->p_memsz - phdr->p_filesz; j++) {
            *dest++ = 0;
        }
    }

    return elf_hdr->e_entry;
}

static void die(int code) {
    while(1);
}
// PDE page directory entry,其实就是页(目录)表项中的标志位，只不过是在扩展模式下
#define PDE_P       (1 << 0)        // 页表项存在位
#define PDE_W       (1 << 1)        // 页表项代表的页面可读可写
#define PDE_PSE     (1 << 7)        // 页扩展标志打开
#define CR4_PSE     (1 << 4)        // 页扩展标志
#define CR0_PG      (1 << 31)       // 分页模式开关标志

// 页表/页目录表要求4k对齐
void enable_page_mode(void) {
    static uint32_t page_dir[1024] __attribute__((aligned(4096))) = {
        [0] = PDE_P | PDE_W | PDE_PSE   // 给第0个表项赋值
    };

    write_cr4(read_cr4() | CR4_PSE);    // page size externsion，打开页扩展（每页4mb大小）
    write_cr3((uint32_t)page_dir);      // 写入页表的位置（目前只实现了一级分页）
    write_cr0(read_cr0() | CR0_PG);     // 打开分页模式开关
}

void load_kernel(void) {
    read_disk(100, 500, (uint8_t *)SYS_KERNEL_LOAD_ADDR);

    uint32_t kernel_entry = reload_elf_file((uint8_t *)SYS_KERNEL_LOAD_ADDR);
    if (kernel_entry == 0) {
        die(-1);
    }
    
    // 开启分页机制
    enable_page_mode();

    ((void (*)(boot_info_t *))kernel_entry)(&boot_info);
    while(1);
}